II. Description of the data source
The data were collected and provided to the NYC Taxi and Limousine Commission (TLC) by technology providers authorized under the Taxicab & Livery Passenger Enhancement Programs (https://www1.nyc.gov/site/tlc/about/tlc-trip-record-data.page).
The For-Hire Vehicle (“FHV”) trip records since 2009 until present including fields capturing the dispatching base license number and the pick-up date, time, and taxi zone location ID. We are focusing on the time period from 2018-01-01 to 2018-12-31, so the data comes in the shape of 200+ million observations, and each row contains one trip infomation.
The base license number is matching with different vehicle companies, so that we will join the base-number file to define the vehicle types, and we only focus on Uber, Lyft, Via at this point.
The NYC Taxi Zones map provided by TLC and published to NYC Open Data(https://data.cityofnewyork.us/Transportation/NYC-Taxi-Zones/d3c5-ddgc). This map shows the NYC taxi zones corresponding to the pick up zomes and drop off zones, or location IDs, included in the FHV trip records. The taxi zones are roughly based on NYC Department of City Planning’s Neighborhood Tabulation Areas (NTAs) and are meant to approximate neighborhoods.
The NYC Weather data is provided by National Centers For Environmental Information (https://www.ncdc.noaa.gov/data-access). NCEI is the world’s largest provider of weather and climate data. Land-based, marine, model, radar, weather balloon, satellite, and paleoclimatic are just a few of the types of datasets available. The weather data we are using is collected from NY Central Park Station (USW00094728) from 2018-01-01 to 2018-12-31, which contains daily weather records such as wind, precipitation, snow and snow depth.
Statistics through June 30, 2018:
- 17.2 GB of raw data
- 200+ million for-hire vehicle total trips
- 365 daily weather records
Existing problem:
- R reads entire data set into RAM all at once. Total 17.2 GB of raw data would not fit in local memory at once.
- R Objects live in memory entirely, which cause slowness for data analysis.
- The TLC publishes base trip record data as submitted by the bases, and we cannot guarantee or confirm their accuracy or completeness.
III. Description of data import / cleaning / transformation
3.1 Libraries and Dependencies
library(tibble) # data wrangling
library(reshape2) # data wrangling
library(tidyr) # data wrangling
library(dplyr) # data manipulation
library(purrr) # data manipulation
library(data.table) # data manipulation
library(ggplot2) # visualisation
#library(ggpubr) # visualisation
library(plotly) # visualisation
library(lubridate) # date and time
#library(rgdal) # import GeoJSON
3.2 Data collection
First of all, we write a shell script to download original data from public websites
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-01.csv >201801.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-02.csv >201802.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-03.csv >201803.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-04.csv >201804.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-05.csv >201805.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-06.csv >201806.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-07.csv >201807.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-08.csv >201808.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-09.csv >201809.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-10.csv >201810.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-11.csv >201811.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-12.csv >201812.csv
3.3 Data Import & Cleaning
Then We use data.table:fread function to speed up reading in data for each month, and identify and select the vehicle company as type based on the license number. At the time, we also export subset monthly data into csv file as back up. we bind all monthly data into tibble format to perform our strucuted data. Each row contains trip information such as pick-up, drop-off date, time, location ID.
Due to local memeory issue in R, we process half-year data at one time, and use aggregation technique to compute results and perform entire year analysis. We will explain more detail below.
base = read.csv("./data/orignal/base_number.csv")$x %>% as.character()
load = function(i)
{
# set file path
x = paste("./data/orignal/2018", i, ".csv") %>% gsub(" ", "", .)
# load orginal data
# identify type
# remove useless columns
temp = fread(x) %>%
filter(Dispatching_base_number %in% c("B02510", "B02800", base)) %>%
mutate(type = ifelse(Dispatching_base_number == "B02510", "Lyft",
ifelse(Dispatching_base_number == "B02800", "Via", "Uber"))) %>%
select(-SR_Flag, -Dispatching_base_number, -Dispatching_base_num) %>%
as.tibble()
# export subset monthly data as back-up
a = paste("./data/", i, ".csv") %>% gsub(" ", "", .)
write.csv(temp, a)
return(data)
}
data = c()
for(i in 1:12)
{
# load original monthly data
temp = load(i)
# combine data
data = bind_rows(data, temp)
# optimize memory usage
remove(temp)
}
3.4 File structure and content
Let’s have an overview of the first 5000 Jan and Dec data. We find the time format is different, so we would like to convert to standard time stamp.
Using summary function to deeply understand the data distribution, and find missing value in DOlocationID
3.6 Data Missing & Outliers
We find there is 91,932 missing value in our dataset. More specific, there are 1,941 Lyft records missing pick-up location, and rest of that are completely bad data records. To conclude accurate analysis, we are going to remove all NA records.
data[which(is.na(data)), ]
Also, due to most companies allow to cancel the order in 2 minutes, we find there are 279,693 records showing the trip duration is less than 2 minutes.
data %>% mutate(duration.large.than.2 = duration > 2 ) %>%
group_by(duration.large.than.2) %>%
count
Then, we investage on those pick-up and drop-off location by using heat mapp.
vals = unique(scales::rescale(df$n))
o = order(vals, decreasing = FALSE)
cols = scales::col_numeric("Reds", domain = NULL)(vals)
colz = setNames(data.frame(vals[o], cols[o]), NULL)
plot_ly(data=df, x=~pick_borough,y=~drop_borough,z=~n, colorscale = colz, type = "heatmap")
To conclude, we believe those data are more likely cancelled trip order, so we are going to remove those as well.
3.7 Data Aggregation
By Solving local memory issue in R, since we are interested in the number of trips, and trip duraction, we don’t have to store all data into R. The idea is that we can process half-by-half year data and aggregate into different levels such as hour, weekday, day, and month. Then, we combine aggregated results to make visualization plots, which are much smaller.
data %>%
mutate(hour = hour(pick), wday = weekdays(pick), type) %>%
group_by(hour, wday, type) %>%
count
data %>%
mutate(month = month.abb[month(pick)]) %>%
group_by(month, type) %>%
summarise(d.med = median(duration))
V. Results
y = read.csv("./1 wday duration.csv") %>% mutate(wday = as.character(wday)) %>% as.tibble()
z = read.csv("./2 wday duration.csv") %>% mutate(wday = as.character(wday)) %>% as.tibble()
inner_join(y, z, by = "wday") %>% mutate(d.med = (d.total.x + d.total.y)/(n.x + n.y)) %>% select(wday, d.med) %>% write.csv(., "wday duration.csv")
bind_rows(y, z) %>% select(-X) %>% write.csv("./day duration.csv")
read.csv("hourly duration.csv") %>%
plot_ly(., x = ~ hour, y = ~ d.med, type ="scatter", mode = 'lines+markers',line = list(color="#2E86C1", width = 4), marker = list(size = 8, color = 'rgba(255, 182, 193, .9)', line = list(color = 'rgba(152, 0, 0, .8)', width = 2,simplyfy = F)), text = ~ paste("Hour: ", hour, '<br>Average duration:', round(d.med,2)), main="average duration vs hour") %>%
layout(title = 'Hourly Median Trip Duration(min)', yaxis = list(title = 'Duration', zeroline = FALSE),
xaxis = list(title = 'Hour',zeroline = FALSE))
'scatter' objects don't have these attributes: 'main'
Valid attributes include:
'type', 'visible', 'showlegend', 'legendgroup', 'opacity', 'name', 'uid', 'ids', 'customdata', 'selectedpoints', 'hoverinfo', 'hoverlabel', 'stream', 'transforms', 'uirevision', 'x', 'x0', 'dx', 'y', 'y0', 'dy', 'stackgroup', 'orientation', 'groupnorm', 'stackgaps', 'text', 'hovertext', 'mode', 'hoveron', 'hovertemplate', 'line', 'connectgaps', 'cliponaxis', 'fill', 'fillcolor', 'marker', 'selected', 'unselected', 'textposition', 'textfont', 'r', 't', 'error_x', 'error_y', 'xcalendar', 'ycalendar', 'xaxis', 'yaxis', 'idssrc', 'customdatasrc', 'hoverinfosrc', 'xsrc', 'ysrc', 'textsrc', 'hovertextsrc', 'hovertemplatesrc', 'textpositionsrc', 'rsrc', 'tsrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'
'scatter' objects don't have these attributes: 'main'
Valid attributes include:
'type', 'visible', 'showlegend', 'legendgroup', 'opacity', 'name', 'uid', 'ids', 'customdata', 'selectedpoints', 'hoverinfo', 'hoverlabel', 'stream', 'transforms', 'uirevision', 'x', 'x0', 'dx', 'y', 'y0', 'dy', 'stackgroup', 'orientation', 'groupnorm', 'stackgaps', 'text', 'hovertext', 'mode', 'hoveron', 'hovertemplate', 'line', 'connectgaps', 'cliponaxis', 'fill', 'fillcolor', 'marker', 'selected', 'unselected', 'textposition', 'textfont', 'r', 't', 'error_x', 'error_y', 'xcalendar', 'ycalendar', 'xaxis', 'yaxis', 'idssrc', 'customdatasrc', 'hoverinfosrc', 'xsrc', 'ysrc', 'textsrc', 'hovertextsrc', 'hovertemplatesrc', 'textpositionsrc', 'rsrc', 'tsrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'
lvl = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
read.csv("wday duration.csv") %>% mutate(wday = ordered(wday, levels = lvl)) %>% arrange(wday) %>%
plot_ly(., x = ~ wday, y = ~ d.med, type ="scatter", mode = 'lines+markers', line = list(width=4,simplyfy = F, color='rgb(114, 186, 59)'), marker = list(size = 8, color = '#8E44AD', line = list(color = '#3498DB', width = 2)), text = ~paste("Weekday: ", wday, '<br>Median duration:', round(d.med,2))) %>%
layout(title = 'Weekday Median Trip Duration(min) for FHV types',
yaxis = list(title = 'Duration',zeroline = FALSE),
xaxis = list(title = 'Weekday',zeroline = FALSE))
LS0tDQp0aXRsZTogIkZpbmFsIFByb2plY3QiDQphdXRob3I6ICJKaWUgTGkiDQpkYXRlOiAiQXByaWwgMjUsIDIwMTkiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KIyMgSS4gSW50cm9kdWN0aW9uDQpUaGlzIGlzIGEgY29tcHJlaGVuc2l2ZSBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIGZvciBiaWxsaW9ucyBvZiBmb3ItaGlyZSB2ZWhpY2xlICgiRkhWIjogVWJlciwgTHlmdCwgVmlhLCBldGMpIHRyaXBzIG9yaWdpbmF0aW5nIGluIE5ldyBZb3JrIENpdHksIGFuZCB3ZSBmb2N1c2Ugb24gdHJpcCBjb3VudHMgYW5kIGR1cmF0aW9uIGluIE5ldyBZb3JrIGNvbXBldGl0aW9uIHdpdGggdGlkeSBSIGFuZCBnZ3Bsb3QyLg0KDQpUaGUgZ29hbCBvZiB0aGlzIGNoYWxsZW5nZSBpcyB0byBsb2FkLCBwcm9jZXNzLCB1bmRlcnN0YW5kIHRoZSBkdXJhdGlvbiBvZiBmaHYgaW4gTllDIGJhc2VkIG9uIGZlYXR1cmVzOiB0cmlwIGxvY2F0aW9uLCBwaWNrLXVwIGFuZCBkcm9wLW9mZiB0aW1lLiBBbHNvLCB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVlbiB0aHJlZSBjb21wYW5pZXMgc3VjaCBhcyBtYXJrZXQgc2hhcmVzLCB0YXJnZXRlZCBjdXN0b21lcnMsIGFuZCBidXNpbmVzcyBzdHJhdGVneS4gRmlyc3RseSwgd2Ugd2lsbCBzdHVkeSBhbmQgdmlzdWFsaXplIHRoZSBvcmlnaW5hbCBkYXRhLCBlbmdpbmVlciBuZXcgZmVhdHVyZXMuIFRoZW4sIHdlIGFkZCBleHRlcm5hbCBOWUMgd2VhdGhlciBkYXRhIHNldHMgdG8gYW5hbHl6ZSB0aGUgaW1wYWN0IG9uIHRoZSB0YXJnZXQgdHJpcCBkdXJhdGlvbi4gRmluYWxseSwgV2UgY29tcGFyZSB0aHJlZSBjb21wYW5pZXMgdHJpcHMgb3ZlciB2YXJpb3VzIHRpbWUgZnJhbWUgb24gdGhlaXIgdGFyZ2V0IHRyaXBfZHVyYXRpb24gdmFsdWVzLg0KDQojIyBJSS4gRGVzY3JpcHRpb24gb2YgdGhlIGRhdGEgc291cmNlDQpUaGUgZGF0YSB3ZXJlIGNvbGxlY3RlZCBhbmQgcHJvdmlkZWQgdG8gdGhlIE5ZQyBUYXhpIGFuZCBMaW1vdXNpbmUgQ29tbWlzc2lvbiAoVExDKSBieSB0ZWNobm9sb2d5IHByb3ZpZGVycyBhdXRob3JpemVkIHVuZGVyIHRoZSBUYXhpY2FiICYgTGl2ZXJ5IFBhc3NlbmdlciBFbmhhbmNlbWVudCBQcm9ncmFtcyAoaHR0cHM6Ly93d3cxLm55Yy5nb3Yvc2l0ZS90bGMvYWJvdXQvdGxjLXRyaXAtcmVjb3JkLWRhdGEucGFnZSkuDQoNClRoZSBGb3ItSGlyZSBWZWhpY2xlICgiRkhWIikgdHJpcCByZWNvcmRzIHNpbmNlIDIwMDkgdW50aWwgcHJlc2VudCBpbmNsdWRpbmcgZmllbGRzIGNhcHR1cmluZyB0aGUgZGlzcGF0Y2hpbmcgYmFzZSBsaWNlbnNlIG51bWJlciBhbmQgdGhlIHBpY2stdXAgZGF0ZSwgdGltZSwgYW5kIHRheGkgem9uZSBsb2NhdGlvbiBJRC4gV2UgYXJlIGZvY3VzaW5nIG9uIHRoZSB0aW1lIHBlcmlvZCBmcm9tICoqMjAxOC0wMS0wMSoqIHRvICoqMjAxOC0xMi0zMSoqLCBzbyB0aGUgZGF0YSBjb21lcyBpbiB0aGUgc2hhcGUgb2YgMjAwKyBtaWxsaW9uIG9ic2VydmF0aW9ucywgYW5kIGVhY2ggcm93IGNvbnRhaW5zIG9uZSB0cmlwIGluZm9tYXRpb24uDQoNClRoZSBiYXNlIGxpY2Vuc2UgbnVtYmVyIGlzIG1hdGNoaW5nIHdpdGggZGlmZmVyZW50IHZlaGljbGUgY29tcGFuaWVzLCBzbyB0aGF0IHdlIHdpbGwgam9pbiB0aGUgYGJhc2UtbnVtYmVyYCBmaWxlIHRvIGRlZmluZSB0aGUgdmVoaWNsZSB0eXBlcywgYW5kIHdlIG9ubHkgZm9jdXMgb24gVWJlciwgTHlmdCwgVmlhIGF0IHRoaXMgcG9pbnQuDQoNClRoZSBOWUMgVGF4aSBab25lcyBtYXAgcHJvdmlkZWQgYnkgVExDIGFuZCBwdWJsaXNoZWQgdG8gTllDIE9wZW4gRGF0YShodHRwczovL2RhdGEuY2l0eW9mbmV3eW9yay51cy9UcmFuc3BvcnRhdGlvbi9OWUMtVGF4aS1ab25lcy9kM2M1LWRkZ2MpLiBUaGlzIG1hcCBzaG93cyB0aGUgTllDIHRheGkgem9uZXMgY29ycmVzcG9uZGluZyB0byB0aGUgcGljayB1cCB6b21lcyBhbmQgZHJvcCBvZmYgem9uZXMsIG9yIGxvY2F0aW9uIElEcywgaW5jbHVkZWQgaW4gdGhlIEZIViB0cmlwIHJlY29yZHMuIFRoZSB0YXhpIHpvbmVzIGFyZSByb3VnaGx5IGJhc2VkIG9uIE5ZQyBEZXBhcnRtZW50IG9mIENpdHkgUGxhbm5pbmcncyBOZWlnaGJvcmhvb2QgVGFidWxhdGlvbiBBcmVhcyAoTlRBcykgYW5kIGFyZSBtZWFudCB0byBhcHByb3hpbWF0ZSBuZWlnaGJvcmhvb2RzLg0KDQpUaGUgTllDIFdlYXRoZXIgZGF0YSBpcyBwcm92aWRlZCBieSBOYXRpb25hbCBDZW50ZXJzIEZvciBFbnZpcm9ubWVudGFsIEluZm9ybWF0aW9uIChodHRwczovL3d3dy5uY2RjLm5vYWEuZ292L2RhdGEtYWNjZXNzKS4gTkNFSSBpcyB0aGUgd29ybGQncyBsYXJnZXN0IHByb3ZpZGVyIG9mIHdlYXRoZXIgYW5kIGNsaW1hdGUgZGF0YS4gTGFuZC1iYXNlZCwgbWFyaW5lLCBtb2RlbCwgcmFkYXIsIHdlYXRoZXIgYmFsbG9vbiwgc2F0ZWxsaXRlLCBhbmQgcGFsZW9jbGltYXRpYyBhcmUganVzdCBhIGZldyBvZiB0aGUgdHlwZXMgb2YgZGF0YXNldHMgYXZhaWxhYmxlLiBUaGUgd2VhdGhlciBkYXRhIHdlIGFyZSB1c2luZyBpcyBjb2xsZWN0ZWQgZnJvbSBOWSBDZW50cmFsIFBhcmsgU3RhdGlvbiAoVVNXMDAwOTQ3MjgpIGZyb20gKioyMDE4LTAxLTAxKiogdG8gKioyMDE4LTEyLTMxKiosIHdoaWNoIGNvbnRhaW5zIGRhaWx5IHdlYXRoZXIgcmVjb3JkcyBzdWNoIGFzIHdpbmQsIHByZWNpcGl0YXRpb24sIHNub3cgYW5kIHNub3cgZGVwdGguDQoNClN0YXRpc3RpY3MgdGhyb3VnaCBKdW5lIDMwLCAyMDE4Og0KDQoqIDE3LjIgR0Igb2YgcmF3IGRhdGENCiogMjAwKyBtaWxsaW9uIGZvci1oaXJlIHZlaGljbGUgdG90YWwgdHJpcHMNCiogMzY1IGRhaWx5IHdlYXRoZXIgcmVjb3Jkcw0KDQpFeGlzdGluZyBwcm9ibGVtOg0KDQoqIFIgcmVhZHMgZW50aXJlIGRhdGEgc2V0IGludG8gUkFNIGFsbCBhdCBvbmNlLiBUb3RhbCAxNy4yIEdCIG9mIHJhdyBkYXRhIHdvdWxkIG5vdCBmaXQgaW4gbG9jYWwgbWVtb3J5IGF0IG9uY2UuDQoqIFIgT2JqZWN0cyBsaXZlIGluIG1lbW9yeSBlbnRpcmVseSwgd2hpY2ggY2F1c2Ugc2xvd25lc3MgZm9yIGRhdGEgYW5hbHlzaXMuDQoqIFRoZSBUTEMgcHVibGlzaGVzIGJhc2UgdHJpcCByZWNvcmQgZGF0YSBhcyBzdWJtaXR0ZWQgYnkgdGhlIGJhc2VzLCBhbmQgd2UgY2Fubm90IGd1YXJhbnRlZSBvciBjb25maXJtIHRoZWlyIGFjY3VyYWN5IG9yIGNvbXBsZXRlbmVzcy4NCg0KDQojIyBJSUkuIERlc2NyaXB0aW9uIG9mIGRhdGEgaW1wb3J0IC8gY2xlYW5pbmcgLyB0cmFuc2Zvcm1hdGlvbg0KIyMjIDMuMSBMaWJyYXJpZXMgYW5kIERlcGVuZGVuY2llcw0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KbGlicmFyeSh0aWJibGUpICMgZGF0YSB3cmFuZ2xpbmcNCmxpYnJhcnkocmVzaGFwZTIpICMgZGF0YSB3cmFuZ2xpbmcNCmxpYnJhcnkodGlkeXIpICMgZGF0YSB3cmFuZ2xpbmcNCmxpYnJhcnkoZHBseXIpICMgZGF0YSBtYW5pcHVsYXRpb24NCmxpYnJhcnkocHVycnIpICMgZGF0YSBtYW5pcHVsYXRpb24NCmxpYnJhcnkoZGF0YS50YWJsZSkgIyBkYXRhIG1hbmlwdWxhdGlvbg0KbGlicmFyeShnZ3Bsb3QyKSAjIHZpc3VhbGlzYXRpb24NCiNsaWJyYXJ5KGdncHVicikgIyB2aXN1YWxpc2F0aW9uDQpsaWJyYXJ5KHBsb3RseSkgIyB2aXN1YWxpc2F0aW9uDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgIyBkYXRlIGFuZCB0aW1lDQojbGlicmFyeShyZ2RhbCkgIyBpbXBvcnQgR2VvSlNPTg0KYGBgDQoNCmBgYHtyLCBlY2hvPUZ9DQpzZXR3ZCgiRTovTllDIFRheGkgUHJvamVjdC8iKQ0KYGBgDQoNCiMjIyAzLjIgRGF0YSBjb2xsZWN0aW9uDQpGaXJzdCBvZiBhbGwsIHdlIHdyaXRlIGEgYHNoZWxsYCBzY3JpcHQgdG8gZG93bmxvYWQgb3JpZ2luYWwgZGF0YSBmcm9tIHB1YmxpYyB3ZWJzaXRlcw0KYGBge3IsIGV2YWw9Rn0NCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTAxLmNzdiA+MjAxODAxLmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDIuY3N2ID4yMDE4MDIuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wMy5jc3YgPjIwMTgwMy5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTA0LmNzdiA+MjAxODA0LmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDUuY3N2ID4yMDE4MDUuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wNi5jc3YgPjIwMTgwNi5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTA3LmNzdiA+MjAxODA3LmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDguY3N2ID4yMDE4MDguY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wOS5jc3YgPjIwMTgwOS5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTEwLmNzdiA+MjAxODEwLmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMTEuY3N2ID4yMDE4MTEuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0xMi5jc3YgPjIwMTgxMi5jc3YNCmBgYA0KDQojIyMgMy4zIERhdGEgSW1wb3J0ICYgQ2xlYW5pbmcNClRoZW4gV2UgdXNlIGBkYXRhLnRhYmxlOmZyZWFkYCBmdW5jdGlvbiB0byBzcGVlZCB1cCByZWFkaW5nIGluIGRhdGEgZm9yIGVhY2ggbW9udGgsIGFuZCBpZGVudGlmeSBhbmQgc2VsZWN0IHRoZSB2ZWhpY2xlIGNvbXBhbnkgYXMgdHlwZSBiYXNlZCBvbiB0aGUgbGljZW5zZSBudW1iZXIuIEF0IHRoZSB0aW1lLCB3ZSBhbHNvIGV4cG9ydCBzdWJzZXQgbW9udGhseSBkYXRhIGludG8gYGNzdmAgZmlsZSBhcyBiYWNrIHVwLiB3ZSBiaW5kIGFsbCBtb250aGx5IGRhdGEgaW50byBgdGliYmxlYCBmb3JtYXQgdG8gcGVyZm9ybSBvdXIgc3RydWN1dGVkIGRhdGEuIEVhY2ggcm93IGNvbnRhaW5zIHRyaXAgaW5mb3JtYXRpb24gc3VjaCBhcyBwaWNrLXVwLCBkcm9wLW9mZiBkYXRlLCB0aW1lLCBsb2NhdGlvbiBJRC4NCg0KRHVlIHRvIGxvY2FsIG1lbWVvcnkgaXNzdWUgaW4gUiwgd2UgcHJvY2VzcyBoYWxmLXllYXIgZGF0YSBhdCBvbmUgdGltZSwgYW5kIHVzZSAqKmFnZ3JlZ2F0aW9uKiogdGVjaG5pcXVlIHRvIGNvbXB1dGUgcmVzdWx0cyBhbmQgcGVyZm9ybSBlbnRpcmUgeWVhciBhbmFseXNpcy4gV2Ugd2lsbCBleHBsYWluIG1vcmUgZGV0YWlsIGJlbG93Lg0KDQpgYGB7ciwgZXZhbD1GfQ0KYmFzZSA9IHJlYWQuY3N2KCIuL2RhdGEvb3JpZ25hbC9iYXNlX251bWJlci5jc3YiKSR4ICU+JSBhcy5jaGFyYWN0ZXIoKQ0KDQpsb2FkID0gZnVuY3Rpb24oaSkNCnsNCiAgIyBzZXQgZmlsZSBwYXRoIA0KICB4ID0gcGFzdGUoIi4vZGF0YS9vcmlnbmFsLzIwMTgiLCBpLCAiLmNzdiIpICU+JSBnc3ViKCIgIiwgIiIsIC4pDQogIA0KICAjIGxvYWQgb3JnaW5hbCBkYXRhDQogICMgaWRlbnRpZnkgdHlwZQ0KICAjIHJlbW92ZSB1c2VsZXNzIGNvbHVtbnMNCiAgdGVtcCA9IGZyZWFkKHgpICU+JQ0KICAgIGZpbHRlcihEaXNwYXRjaGluZ19iYXNlX251bWJlciAlaW4lIGMoIkIwMjUxMCIsICJCMDI4MDAiLCBiYXNlKSkgJT4lDQogICAgbXV0YXRlKHR5cGUgPSBpZmVsc2UoRGlzcGF0Y2hpbmdfYmFzZV9udW1iZXIgPT0gIkIwMjUxMCIsICJMeWZ0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoRGlzcGF0Y2hpbmdfYmFzZV9udW1iZXIgPT0gIkIwMjgwMCIsICJWaWEiLCAiVWJlciIpKSkgJT4lDQogICAgc2VsZWN0KC1TUl9GbGFnLCAtRGlzcGF0Y2hpbmdfYmFzZV9udW1iZXIsIC1EaXNwYXRjaGluZ19iYXNlX251bSkgJT4lDQogICAgYXMudGliYmxlKCkNCiAgDQogICMgZXhwb3J0IHN1YnNldCBtb250aGx5IGRhdGEgYXMgYmFjay11cA0KICBhID0gcGFzdGUoIi4vZGF0YS8iLCBpLCAiLmNzdiIpICU+JSBnc3ViKCIgIiwgIiIsIC4pDQogIHdyaXRlLmNzdih0ZW1wLCBhKQ0KDQogIHJldHVybihkYXRhKQ0KfQ0KDQpkYXRhID0gYygpDQpmb3IoaSBpbiAxOjEyKQ0Kew0KICAjIGxvYWQgb3JpZ2luYWwgbW9udGhseSBkYXRhDQogIHRlbXAgPSBsb2FkKGkpDQogIA0KICAjIGNvbWJpbmUgZGF0YQ0KICBkYXRhID0gYmluZF9yb3dzKGRhdGEsIHRlbXApDQogIA0KICAjIG9wdGltaXplIG1lbW9yeSB1c2FnZQ0KICByZW1vdmUodGVtcCkNCn0NCmBgYA0KDQojIyMgMy40IEZpbGUgc3RydWN0dXJlIGFuZCBjb250ZW50DQpMZXQncyBoYXZlIGFuIG92ZXJ2aWV3IG9mIHRoZSBmaXJzdCA1MDAwIGBKYW5gIGFuZCBgRGVjYCBkYXRhLiBXZSBmaW5kIHRoZSB0aW1lIGZvcm1hdCBpcyBkaWZmZXJlbnQsIHNvIHdlIHdvdWxkIGxpa2UgdG8gY29udmVydCB0byBzdGFuZGFyZCB0aW1lIHN0YW1wLg0KDQpgYGB7ciwgZWNobz1GfQ0KSmFuID0gcmVhZC5jc3YoIi4vZGF0YS8wMS5jc3YiKSAlPiUgc2VsZWN0KC1YKQ0KRGVjID0gcmVhZC5jc3YoIi4vZGF0YS8wMTIuY3N2IikgJT4lIHNlbGVjdCgtWCkNCg0KSmFuDQpEZWMNCmBgYA0KDQpVc2luZyBgc3VtbWFyeWAgZnVuY3Rpb24gdG8gZGVlcGx5IHVuZGVyc3RhbmQgdGhlIGRhdGEgZGlzdHJpYnV0aW9uLCBhbmQgZmluZCBtaXNzaW5nIHZhbHVlIGluIGBET2xvY2F0aW9uSURgDQpgYGB7ciwgZWNobz1GfQ0KcmVhZC5jc3YoIi4vc3VtbWFyeSBKYW4uY3N2IikgJT4lIHNlbGVjdCgtWCkgJT4lIGFzLnRpYmJsZSgpIA0KcmVhZC5jc3YoIi4vc3VtbWFyeSBEZWMuY3N2IikgJT4lIHNlbGVjdCgtWCkgJT4lIGFzLnRpYmJsZSgpDQpgYGANCg0KIyMjIDMuNSBEYXRhIFRyYW5zZm9ybWF0aW9uDQpOZXh0LCB3ZSB1c2UgYGx1YnJpZGF0ZTp5bWRfaG1zYCBhbmQgYGx1YnJpZGF0ZTptZHlfaG1zYCB0cmFuc2Zvcm1hdCBzdHJpbmcgdG8gc3RhbmRhcmQgdGltZSBzdGFtcCB2YXJpYWJsZXMsIGFuZCBjYWx1Y2F0ZSB0aGUgdHJpcCBkdXJhdGlvbiBpbiAqKm1pbnV0ZSoqIGJ5IHNidXN0cmFjdGluZyBkcm9wLW9mZiB0aW1lIGFuZCBwaWNrLXVwIHRpbWUuIEFsc28sIHdlIGZhY3Rvcml6ZSB0aGUgY29tcGFueSB0eXBlcyB0byBzYXZlIG1lbW9yeSB1c2FnZSBhbmQgZnVydHVyZSB2aXN1YWxpemF0aW9uLg0KDQpgYGB7cn0NCiMgSmFuLU5vdg0KSmFuICU+JQ0KbXV0YXRlKHBpY2sgPSB5bWRfaG1zKFBpY2t1cF9EYXRlVGltZSksIGRyb3AgPSB5bWRfaG1zKERyb3BPZmZfZGF0ZXRpbWUpLCBkdXJhdGlvbiA9IGFzLm51bWVyaWMoZHJvcCAtIHBpY2spLzYwLCB0eXBlID0gZmFjdG9yKHR5cGUpKSAlPiUNCnNlbGVjdCgtVjEsIC1QaWNrdXBfRGF0ZVRpbWUsIC1Ecm9wT2ZmX2RhdGV0aW1lKQ0KDQojIG9ubHkgZm9yIERlYw0KRGVjICU+JQ0KbXV0YXRlKHBpY2sgPSBtZHlfaG0oUGlja3VwX0RhdGVUaW1lKSwgZHJvcCA9IG1keV9obShEcm9wT2ZmX2RhdGV0aW1lKSwgZHVyYXRpb24gPSBhcy5udW1lcmljKGRyb3AgLSBwaWNrKS82MCwgdHlwZSA9IGZhY3Rvcih0eXBlKSkgJT4lDQpzZWxlY3QoLVYxLCAtUGlja3VwX0RhdGVUaW1lLCAtRHJvcE9mZl9kYXRldGltZSkNCmBgYA0KDQojIyMgMy42IERhdGEgTWlzc2luZyAmIE91dGxpZXJzDQoNCldlIGZpbmQgdGhlcmUgaXMgOTEsOTMyIG1pc3NpbmcgdmFsdWUgaW4gb3VyIGRhdGFzZXQuIE1vcmUgc3BlY2lmaWMsIHRoZXJlIGFyZSAxLDk0MSBMeWZ0IHJlY29yZHMgbWlzc2luZyBwaWNrLXVwIGxvY2F0aW9uLCBhbmQgcmVzdCBvZiB0aGF0IGFyZSBjb21wbGV0ZWx5IGJhZCBkYXRhIHJlY29yZHMuIFRvIGNvbmNsdWRlIGFjY3VyYXRlIGFuYWx5c2lzLCB3ZSBhcmUgZ29pbmcgdG8gcmVtb3ZlIGFsbCBgTkFgIHJlY29yZHMuDQpgYGB7ciwgZXZhbD1GfQ0KZGF0YVt3aGljaChpcy5uYShkYXRhKSksIF0NCmBgYA0KDQpgYGB7ciwgZWNobz1GfQ0KeCA9IHJlYWQuY3N2KCIuL21pc3NpbmcuY3N2IikgJT4lIGFzLnRpYmJsZSgpICU+JSBtdXRhdGUocGljayA9IG1keV9obShwaWNrKSwgZHJvcCA9IG1keV9obShkcm9wKSkNCngNCnRhaWwoeCkNCmBgYA0KDQpBbHNvLCBkdWUgdG8gbW9zdCBjb21wYW5pZXMgYWxsb3cgdG8gY2FuY2VsIHRoZSBvcmRlciBpbiAyIG1pbnV0ZXMsIHdlIGZpbmQgdGhlcmUgYXJlIDI3OSw2OTMgcmVjb3JkcyBzaG93aW5nIHRoZSB0cmlwIGR1cmF0aW9uIGlzIGxlc3MgdGhhbiAyIG1pbnV0ZXMuDQoNCmBgYHtyLCBldmFsPUZ9DQpkYXRhICU+JSBtdXRhdGUoZHVyYXRpb24ubGFyZ2UudGhhbi4yID0gZHVyYXRpb24gPiAyICkgJT4lDQogIGdyb3VwX2J5KGR1cmF0aW9uLmxhcmdlLnRoYW4uMikgJT4lDQogIGNvdW50DQpgYGANCg0KYGBge3IsIGVjaG89Rn0NCnJlYWQuY3N2KCIuLzJtaW5fY291bnRzLmNzdiIpICU+JSBhcy50aWJibGUoKQ0KYGBgDQoNCg0KYGBge3IsIGVjaG89Rn0NCmRmID0gcmVhZC5jc3YoIi4vZGF0YS9sZXNzX3RoYW5fMi5jc3YiKQ0KcGljayA9IHJlYWQuY3N2KCIuL2RpY3Rpb25hcnlfcGlja3VwLmNzdiIpICU+JSBhcy50aWJibGUoKQ0KZHJvcCA9IHJlYWQuY3N2KCIuL2RpY3Rpb25hcnlfZHJvcG9mZi5jc3YiKSAlPiUgYXMudGliYmxlKCkNCg0KbWF0Y2hfem9uZSA9IGZ1bmN0aW9uKGRmLGRpY3Rpb25hcnlfcGlja3VwLGRpY3Rpb25hcnlfZHJvcG9mZil7DQoNCiAgbWF0Y2hlZC5kZiA9IGRmICU+JSANCiAgIGxlZnRfam9pbihkaWN0aW9uYXJ5X3BpY2t1cCxieT0iUFVsb2NhdGlvbklEIikgJT4lIA0KICAgbXV0YXRlKHBpY2tfYm9yb3VnaCA9IGJvcm91Z2gpICU+JSANCiAgIHNlbGVjdCgtYm9yb3VnaCklPiUNCiAgIGxlZnRfam9pbihkaWN0aW9uYXJ5X2Ryb3BvZmYsYnk9IkRPbG9jYXRpb25JRCIpICU+JSANCiAgIG11dGF0ZShkcm9wX2Jvcm91Z2ggPSBib3JvdWdoKSAlPiUgIA0KICBzZWxlY3QoLWJvcm91Z2gsLXR5cGUsLXBpY2ssLWRyb3ApICU+JSANCiAgIGZpbHRlcighaXMubmEoZHJvcF9ib3JvdWdoKSkgJT4lIA0KICAgZmlsdGVyKCFpcy5uYShwaWNrX2Jvcm91Z2gpKSAlPiUgDQogIGdyb3VwX2J5KHBpY2tfYm9yb3VnaCxkcm9wX2Jvcm91Z2gpIA0KDQogIHJldHVybihtYXRjaGVkLmRmKQ0KfQ0KDQpkZiA9IG1hdGNoX3pvbmUoZGYscGljayxkcm9wKSAlPiUgY291bnQoKQ0KYGBgDQoNClRoZW4sIHdlIGludmVzdGFnZSBvbiB0aG9zZSBwaWNrLXVwIGFuZCBkcm9wLW9mZiBsb2NhdGlvbiBieSB1c2luZyAqKmhlYXQgbWFwcCoqLiANCmBgYHtyfQ0KdmFscyA9IHVuaXF1ZShzY2FsZXM6OnJlc2NhbGUoZGYkbikpDQpvID0gb3JkZXIodmFscywgZGVjcmVhc2luZyA9IEZBTFNFKQ0KY29scyA9IHNjYWxlczo6Y29sX251bWVyaWMoIlJlZHMiLCBkb21haW4gPSBOVUxMKSh2YWxzKQ0KY29seiA9IHNldE5hbWVzKGRhdGEuZnJhbWUodmFsc1tvXSwgY29sc1tvXSksIE5VTEwpDQpwbG90X2x5KGRhdGE9ZGYsIHg9fnBpY2tfYm9yb3VnaCx5PX5kcm9wX2Jvcm91Z2gsej1+biwgY29sb3JzY2FsZSA9IGNvbHosIHR5cGUgPSAiaGVhdG1hcCIpDQpgYGANCg0KVG8gY29uY2x1ZGUsIHdlIGJlbGlldmUgdGhvc2UgZGF0YSBhcmUgbW9yZSBsaWtlbHkgY2FuY2VsbGVkIHRyaXAgb3JkZXIsIHNvIHdlIGFyZSBnb2luZyB0byByZW1vdmUgdGhvc2UgYXMgd2VsbC4NCg0KDQpgYGB7ciwgZWNobz1GfQ0KcmVhZC5jc3YoIi4vMm1pbl9jb3VudHMuY3N2IikgJT4lIGFzLnRpYmJsZSgpDQpgYGANCg0KIyMjIDMuNyBEYXRhIEFnZ3JlZ2F0aW9uDQpCeSBTb2x2aW5nIGxvY2FsIG1lbW9yeSBpc3N1ZSBpbiBSLCBzaW5jZSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgbnVtYmVyIG9mIHRyaXBzLCBhbmQgdHJpcCBkdXJhY3Rpb24sIHdlIGRvbid0IGhhdmUgdG8gc3RvcmUgYWxsIGRhdGEgaW50byBSLiBUaGUgaWRlYSBpcyB0aGF0IHdlIGNhbiBwcm9jZXNzIGhhbGYtYnktaGFsZiB5ZWFyIGRhdGEgYW5kIGFnZ3JlZ2F0ZSBpbnRvIGRpZmZlcmVudCBsZXZlbHMgc3VjaCBhcyBob3VyLCB3ZWVrZGF5LCBkYXksIGFuZCBtb250aC4gVGhlbiwgd2UgY29tYmluZSBhZ2dyZWdhdGVkIHJlc3VsdHMgdG8gbWFrZSB2aXN1YWxpemF0aW9uIHBsb3RzLCB3aGljaCBhcmUgbXVjaCBzbWFsbGVyLg0KDQpgYGB7ciwgZXZhbD1GfQ0KZGF0YSAlPiUNCiAgICBtdXRhdGUoaG91ciA9IGhvdXIocGljayksIHdkYXkgPSB3ZWVrZGF5cyhwaWNrKSwgdHlwZSkgJT4lDQogICAgZ3JvdXBfYnkoaG91ciwgd2RheSwgdHlwZSkgJT4lDQogICAgY291bnQNCmBgYA0KDQpgYGB7ciwgZWNobz1GfQ0KcmVhZC5jc3YoImNvdW50cyBieSBoX3drX3R5cGUuY3N2IikgJT4lIHNlbGVjdCgtWCkgJT4lIGFzLnRpYmJsZSgpDQpgYGANCg0KYGBge3IsIGV2YWw9Rn0NCmRhdGEgJT4lDQogICAgbXV0YXRlKG1vbnRoID0gbW9udGguYWJiW21vbnRoKHBpY2spXSkgJT4lDQogICAgZ3JvdXBfYnkobW9udGgsIHR5cGUpICU+JQ0KICAgIHN1bW1hcmlzZShkLm1lZCA9IG1lZGlhbihkdXJhdGlvbikpDQpgYGANCg0KDQpgYGB7ciwgZWNobz1GfQ0KcmVhZC5jc3YoIm1vbnRoIHR5cGUgZHVyYXRpb24uY3N2IikgJT4lIHNlbGVjdCgtWCkgJT4lIGFzLnRpYmJsZSgpDQpgYGANCg0KIyMgVi4gUmVzdWx0cw0KYGBge3IsIGV2YWw9Rn0NCnkgPSByZWFkLmNzdigiLi8xIHdkYXkgZHVyYXRpb24uY3N2IikgJT4lIG11dGF0ZSh3ZGF5ID0gYXMuY2hhcmFjdGVyKHdkYXkpKSAlPiUgYXMudGliYmxlKCkNCg0KeiA9IHJlYWQuY3N2KCIuLzIgd2RheSBkdXJhdGlvbi5jc3YiKSAlPiUgbXV0YXRlKHdkYXkgPSBhcy5jaGFyYWN0ZXIod2RheSkpICU+JSBhcy50aWJibGUoKQ0KDQppbm5lcl9qb2luKHksIHosIGJ5ID0gIndkYXkiKSAlPiUgbXV0YXRlKGQubWVkID0gKGQudG90YWwueCArIGQudG90YWwueSkvKG4ueCArIG4ueSkpICU+JSBzZWxlY3Qod2RheSwgZC5tZWQpICU+JSB3cml0ZS5jc3YoLiwgIndkYXkgZHVyYXRpb24uY3N2IikNCg0KYmluZF9yb3dzKHksIHopICU+JSBzZWxlY3QoLVgpICU+JSB3cml0ZS5jc3YoIi4vZGF5IGR1cmF0aW9uLmNzdiIpDQoNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQpyZWFkLmNzdigiaG91cmx5IGR1cmF0aW9uLmNzdiIpICU+JQ0KcGxvdF9seSguLCB4ID0gfiBob3VyLCB5ID0gfiBkLm1lZCwgdHlwZSA9InNjYXR0ZXIiLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLGxpbmUgPSBsaXN0KGNvbG9yPSIjMkU4NkMxIiwgd2lkdGggPSA0KSwgbWFya2VyID0gbGlzdChzaXplID0gOCwgY29sb3IgPSAncmdiYSgyNTUsIDE4MiwgMTkzLCAuOSknLCBsaW5lID0gbGlzdChjb2xvciA9ICdyZ2JhKDE1MiwgMCwgMCwgLjgpJywgd2lkdGggPSAyLHNpbXBseWZ5ID0gRikpLCB0ZXh0ID0gfiBwYXN0ZSgiSG91cjogIiwgaG91ciwgJzxicj5BdmVyYWdlIGR1cmF0aW9uOicsIHJvdW5kKGQubWVkLDIpKSwgbWFpbj0iYXZlcmFnZSBkdXJhdGlvbiB2cyBob3VyIikgJT4lDQogIGxheW91dCh0aXRsZSA9ICdIb3VybHkgTWVkaWFuIFRyaXAgRHVyYXRpb24obWluKScsIHlheGlzID0gbGlzdCh0aXRsZSA9ICdEdXJhdGlvbicsIHplcm9saW5lID0gRkFMU0UpLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gJ0hvdXInLHplcm9saW5lID0gRkFMU0UpKQ0KYGBgDQoNCg0KYGBge3J9DQpsdmwgPSBjKCJNb25kYXkiLCAiVHVlc2RheSIsICJXZWRuZXNkYXkiLCAiVGh1cnNkYXkiLCAiRnJpZGF5IiwgIlNhdHVyZGF5IiwgIlN1bmRheSIpDQoNCnJlYWQuY3N2KCJ3ZGF5IGR1cmF0aW9uLmNzdiIpICU+JSBtdXRhdGUod2RheSA9IG9yZGVyZWQod2RheSwgbGV2ZWxzID0gbHZsKSkgJT4lIGFycmFuZ2Uod2RheSkgJT4lDQogIHBsb3RfbHkoLiwgeCA9IH4gd2RheSwgeSA9IH4gZC5tZWQsIHR5cGUgPSJzY2F0dGVyIiwgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbGluZSA9IGxpc3Qod2lkdGg9NCxzaW1wbHlmeSA9IEYsIGNvbG9yPSdyZ2IoMTE0LCAxODYsIDU5KScpLCBtYXJrZXIgPSBsaXN0KHNpemUgPSA4LCBjb2xvciA9ICcjOEU0NEFEJywgbGluZSA9IGxpc3QoY29sb3IgPSAnIzM0OThEQicsIHdpZHRoID0gMikpLCB0ZXh0ID0gfnBhc3RlKCJXZWVrZGF5OiAiLCB3ZGF5LCAnPGJyPk1lZGlhbiBkdXJhdGlvbjonLCByb3VuZChkLm1lZCwyKSkpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnV2Vla2RheSBNZWRpYW4gVHJpcCBEdXJhdGlvbihtaW4pIGZvciBGSFYgdHlwZXMnLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0R1cmF0aW9uJyx6ZXJvbGluZSA9IEZBTFNFKSwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdXZWVrZGF5Jyx6ZXJvbGluZSA9IEZBTFNFKSkNCmBgYA0KDQoNCg==